﻿using System;
using System.Collections;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Unity.EditorCoroutines.Editor;
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;

#else
using UnityEngine.Experimental.UIElements;
#endif

namespace ZhenG.Toolbar
{
    /// <summary>
    /// Toolbar extension which provides new functionalities into classic Unity's scene toolbar.
    /// </summary>
    [InitializeOnLoad]
    public static class ToolbarEditor
    {
        private static class ReflectionData
        {
            public static readonly Type ToolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
            public static readonly Type GuiViewType = typeof(Editor).Assembly.GetType("UnityEditor.GUIView");
            public static readonly Type ImGuiContainerType = typeof(IMGUIContainer);

#if UNITY_2020_1_OR_NEWER
            public static readonly Type BackendType = typeof(Editor).Assembly.GetType("UnityEditor.IWindowBackend");
            public static readonly PropertyInfo GuiBackend = GuiViewType.GetProperty("windowBackend", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            public static readonly PropertyInfo VisualTree = BackendType.GetProperty("visualTree", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#else
            public static readonly PropertyInfo VisualTree = GuiViewType.GetProperty("visualTree", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#endif

            public static readonly FieldInfo OnGuiHandler = ImGuiContainerType.GetField("m_OnGUIHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            public static readonly MethodInfo RepaintMethod = ToolbarType.GetMethod("RepaintToolbar", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
        }

        static ToolbarEditor()
        {
            EditorCoroutineUtility.StartCoroutineOwnerless(InitializeToolbar());
        }

        private static UnityEngine.Object _toolbar;
        private static bool _isInitialized;

        private static ToolbarExtend _toolbarExtend;
        public static bool IsToolbarAllowed { get; set; } = true;
        public static float FromToolsOffset { get; set; } = 400.0f;
        public static float FromStripOffset { get; set; } = 150.0f;
        
        public static bool IsLeftToolbarValid => _toolbar != null && _toolbarExtend != null;
        public static bool IsRightToolbarValid => _toolbar != null && _toolbarExtend != null;

        private static IEnumerator InitializeToolbar()
        {
            while (_toolbar == null)
            {
                var toolbars = Resources.FindObjectsOfTypeAll(ReflectionData.ToolbarType);
                if (toolbars != null && toolbars.Length > 0)
                {
                    _toolbar = toolbars[0];
                }

                yield return null;
            }

            SetupToolbar();
        }

        private static void SetupToolbar()
        {
            if (_isInitialized)
                return;
            if (_toolbarExtend == null)
                _toolbarExtend = new ToolbarExtend();
#if UNITY_2021_1_OR_NEWER
            SetupModernToolbar();
#else
            SetupLegacyToolbar();
#endif

            _isInitialized = true;
        }

#if UNITY_2021_1_OR_NEWER
        private static void SetupModernToolbar()
        {
            var rootField = _toolbar.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
            var root = rootField.GetValue(_toolbar) as VisualElement;

            SetupLeftToolbarZone(root);
            SetupRightToolbarZone(root);
        }

        private static void SetupLeftToolbarZone(VisualElement root)
        {
            var toolbarLeftZone = root.Q("ToolbarZoneLeftAlign");
            if (toolbarLeftZone == null) return;

            var element = new VisualElement() { style = { flexGrow = 1, flexDirection = FlexDirection.Row } };

            var container = new IMGUIContainer();
            container.style.flexGrow = 1;
            container.onGUIHandler += OnGuiLeft;
            element.Add(container);
            toolbarLeftZone.Add(element);
        }

        private static void SetupRightToolbarZone(VisualElement root)
        {
            var toolbarRightZone = root.Q("ToolbarZoneRightAlign");
            if (toolbarRightZone == null) return;

            var rightElement = new VisualElement() { style = { flexGrow = 1, flexDirection = FlexDirection.Row } };

            var rightContainer = new IMGUIContainer();
            rightContainer.style.flexGrow = 1;
            rightContainer.onGUIHandler += OnGuiRight;
            rightElement.Add(rightContainer);
            toolbarRightZone.Add(rightElement);
        }
#else
        private static void SetupLegacyToolbar()
        {
            VisualElement elements = null;

#if UNITY_2020_1_OR_NEWER
            var backend = ReflectionData.GuiBackend.GetValue(_toolbar);
            elements = ReflectionData.VisualTree.GetValue(backend, null) as VisualElement;
#elif UNITY_2019_1_OR_NEWER
            elements = ReflectionData.VisualTree.GetValue(_toolbar, null) as VisualElement;
#else
            var visualTree = ReflectionData.VisualTree.GetValue(_toolbar, null);
            elements = visualTree as VisualElement;
#endif

            if (elements == null || elements.childCount == 0) return;

            IMGUIContainer container = null;

#if UNITY_2019_1_OR_NEWER
            container = elements[0] as IMGUIContainer;
#else
            container = elements[0] as IMGUIContainer;
#endif

            if (container == null) return;

            var handler = ReflectionData.OnGuiHandler.GetValue(container) as Action;
            if (handler != null)
            {
                handler -= OnGuiLeft;
                handler += OnGuiLeft;
                ReflectionData.OnGuiHandler.SetValue(container, handler);
            }
        }
#endif

        private static void OnGuiLeft()
        {
            if (!IsToolbarAllowed || !IsLeftToolbarValid)
            {
                return;
            }

#if UNITY_2021_1_OR_NEWER
            using (new GUILayout.HorizontalScope())
            {
                _toolbarExtend.DrawLeftToolbar();
            }
#else
            DrawLegacyLeftToolbar();
#endif
        }

#if !UNITY_2021_1_OR_NEWER
        private static void DrawLegacyLeftToolbar()
        {
            var screenWidth = EditorGUIUtility.currentViewWidth;
            var toolbarRect = new Rect(0, 0, screenWidth, Style.RowHeight);
            
            toolbarRect.xMin += FromToolsOffset;
            toolbarRect.xMax = (screenWidth - FromStripOffset) / 2;
            toolbarRect.xMin += Style.Spacing;
            toolbarRect.xMax -= Style.Spacing;
            toolbarRect.yMin += Style.TopPadding;
            toolbarRect.yMax -= Style.BottomPadding;

            if (toolbarRect.width <= 0)
            {
                return;
            }

            using (new GUILayout.AreaScope(toolbarRect))
            {
                using (new GUILayout.HorizontalScope())
                {
                    DrawLeftToolbar?.Invoke();
                }
            }
        }
#endif

        private static void OnGuiRight()
        {
            if (!IsToolbarAllowed || !IsRightToolbarValid)
            {
                return;
            }

            using (new EditorGUILayout.HorizontalScope())
            {
                _toolbarExtend.DrawRightToolbar();
            }
        }

        public static void Repaint()
        {
            if (_toolbar == null)
            {
                return;
            }

            ReflectionData.RepaintMethod?.Invoke(_toolbar, null);
        }
    }
}